// Copyright (c) 2012-2016 The Bitcoin Core developers
// Copyright (c) 2017-2019 The Raven Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "dbwrapper.h"
#include "uint256.h"
#include "random.h"
#include "test/test_raven.h"

#include <boost/test/unit_test.hpp>

// Test if a string consists entirely of null characters
bool is_null_key(const std::vector<unsigned char> &key)
{
    bool isnull = true;

    for (unsigned int i = 0; i < key.size(); i++)
        isnull &= (key[i] == '\x00');

    return isnull;
}

BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup)

    BOOST_AUTO_TEST_CASE(dbwrapper_test)
    {
        BOOST_TEST_MESSAGE("Running dbWrapper Test");

        // Perform tests both obfuscated and non-obfuscated.
        for (bool obfuscate : {false, true})
        {
            fs::path ph = fs::temp_directory_path() / fs::unique_path();
            CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
            char key = 'k';
            uint256 in = InsecureRand256();
            uint256 res;

            // Ensure that we're doing real obfuscation when obfuscate=true
            BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));

            BOOST_CHECK(dbw.Write(key, in));
            BOOST_CHECK(dbw.Read(key, res));
            BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
        }
    }

// Test batch operations
    BOOST_AUTO_TEST_CASE(dbwrapper_batch_test)
    {
        BOOST_TEST_MESSAGE("Running dbWrapper Batch Test");

        // Perform tests both obfuscated and non-obfuscated.
        for (bool obfuscate : {false, true})
        {
            fs::path ph = fs::temp_directory_path() / fs::unique_path();
            CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);

            char key = 'i';
            uint256 in = InsecureRand256();
            char key2 = 'j';
            uint256 in2 = InsecureRand256();
            char key3 = 'k';
            uint256 in3 = InsecureRand256();

            uint256 res;
            CDBBatch batch(dbw);

            batch.Write(key, in);
            batch.Write(key2, in2);
            batch.Write(key3, in3);

            // Remove key3 before it's even been written
            batch.Erase(key3);

            dbw.WriteBatch(batch);

            BOOST_CHECK(dbw.Read(key, res));
            BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
            BOOST_CHECK(dbw.Read(key2, res));
            BOOST_CHECK_EQUAL(res.ToString(), in2.ToString());

            // key3 should've never been written
            BOOST_CHECK(dbw.Read(key3, res) == false);
        }
    }

    BOOST_AUTO_TEST_CASE(dbwrapper_iterator_test)
    {
        BOOST_TEST_MESSAGE("Running dbWrapper Iterator Test");

        // Perform tests both obfuscated and non-obfuscated.
        for (bool obfuscate : {false, true})
        {
            fs::path ph = fs::temp_directory_path() / fs::unique_path();
            CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);

            // The two keys are intentionally chosen for ordering
            char key = 'j';
            uint256 in = InsecureRand256();
            BOOST_CHECK(dbw.Write(key, in));
            char key2 = 'k';
            uint256 in2 = InsecureRand256();
            BOOST_CHECK(dbw.Write(key2, in2));

            std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper &>(dbw).NewIterator());

            // Be sure to seek past the obfuscation key (if it exists)
            it->Seek(key);

            char key_res;
            uint256 val_res;

            it->GetKey(key_res);
            it->GetValue(val_res);
            BOOST_CHECK_EQUAL(key_res, key);
            BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString());

            it->Next();

            it->GetKey(key_res);
            it->GetValue(val_res);
            BOOST_CHECK_EQUAL(key_res, key2);
            BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString());

            it->Next();
            BOOST_CHECK_EQUAL(it->Valid(), false);
        }
    }

// Test that we do not obfuscation if there is existing data.
    BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate_test)
    {
        BOOST_TEST_MESSAGE("Running Existing Data No Obfuscate Test");

        // We're going to share this fs::path between two wrappers
        fs::path ph = fs::temp_directory_path() / fs::unique_path();
        create_directories(ph);

        // Set up a non-obfuscated wrapper to write some initial data.
        CDBWrapper *dbw = new CDBWrapper(ph, (1 << 10), false, false, false);
        char key = 'k';
        uint256 in = InsecureRand256();
        uint256 res;

        BOOST_CHECK(dbw->Write(key, in));
        BOOST_CHECK(dbw->Read(key, res));
        BOOST_CHECK_EQUAL(res.ToString(), in.ToString());

        // Call the destructor to free leveldb LOCK
        delete dbw;
        dbw = nullptr;

        // Now, set up another wrapper that wants to obfuscate the same directory
        CDBWrapper odbw(ph, (1 << 10), false, false, true);

        // Check that the key/val we wrote with unobfuscated wrapper exists and
        // is readable.
        uint256 res2;
        BOOST_CHECK(odbw.Read(key, res2));
        BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());

        BOOST_CHECK(!odbw.IsEmpty()); // There should be existing data
        BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscateKey(odbw))); // The key should be an empty string

        uint256 in2 = InsecureRand256();
        uint256 res3;

        // Check that we can write successfully
        BOOST_CHECK(odbw.Write(key, in2));
        BOOST_CHECK(odbw.Read(key, res3));
        BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
    }

// Ensure that we start obfuscating during a reindex.
    BOOST_AUTO_TEST_CASE(existing_data_reindex_test)
    {
        BOOST_TEST_MESSAGE("Running Existing Data ReIndex Test Test");

        // We're going to share this fs::path between two wrappers
        fs::path ph = fs::temp_directory_path() / fs::unique_path();
        create_directories(ph);

        // Set up a non-obfuscated wrapper to write some initial data.
        CDBWrapper *dbw = new CDBWrapper(ph, (1 << 10), false, false, false);
        char key = 'k';
        uint256 in = InsecureRand256();
        uint256 res;

        BOOST_CHECK(dbw->Write(key, in));
        BOOST_CHECK(dbw->Read(key, res));
        BOOST_CHECK_EQUAL(res.ToString(), in.ToString());

        // Call the destructor to free leveldb LOCK
        delete dbw;
        dbw = nullptr;

        // Simulate a -reindex by wiping the existing data store
        CDBWrapper odbw(ph, (1 << 10), false, true, true);

        // Check that the key/val we wrote with unobfuscated wrapper doesn't exist
        uint256 res2;
        BOOST_CHECK(!odbw.Read(key, res2));
        BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));

        uint256 in2 = InsecureRand256();
        uint256 res3;

        // Check that we can write successfully
        BOOST_CHECK(odbw.Write(key, in2));
        BOOST_CHECK(odbw.Read(key, res3));
        BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
    }

    BOOST_AUTO_TEST_CASE(iterator_ordering_test)
    {
        BOOST_TEST_MESSAGE("Running Iterator Ordering Test");

        fs::path ph = fs::temp_directory_path() / fs::unique_path();
        CDBWrapper dbw(ph, (1 << 20), true, false, false);
        for (int x = 0x00; x < 256; ++x)
        {
            uint8_t key = x;
            uint32_t value = x * x;
            if (!(x & 1)) BOOST_CHECK(dbw.Write(key, value));
        }

        // Check that creating an iterator creates a snapshot
        std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper &>(dbw).NewIterator());

        for (int x = 0x00; x < 256; ++x)
        {
            uint8_t key = x;
            uint32_t value = x * x;
            if (x & 1) BOOST_CHECK(dbw.Write(key, value));
        }

        for (int seek_start : {0x00, 0x80})
        {
            it->Seek((uint8_t) seek_start);
            for (int x = seek_start; x < 255; ++x)
            {
                uint8_t key;
                uint32_t value;
                BOOST_CHECK(it->Valid());
                if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure
                    break;
                BOOST_CHECK(it->GetKey(key));
                if (x & 1)
                {
                    BOOST_CHECK_EQUAL(key, x + 1);
                    continue;
                }
                BOOST_CHECK(it->GetValue(value));
                BOOST_CHECK_EQUAL(key, x);
                BOOST_CHECK_EQUAL(value, (uint64_t)(x * x));
                it->Next();
            }
            BOOST_CHECK(!it->Valid());
        }
    }

    struct StringContentsSerializer
    {
        // Used to make two serialized objects the same while letting them have a different lengths
        // This is a terrible idea
        std::string str;

        StringContentsSerializer()
        {}

        explicit StringContentsSerializer(const std::string &inp) : str(inp)
        {}

        StringContentsSerializer &operator+=(const std::string &s)
        {
            str += s;
            return *this;
        }

        StringContentsSerializer &operator+=(const StringContentsSerializer &s)
        { return *this += s.str; }

        ADD_SERIALIZE_METHODS;

        template<typename Stream, typename Operation>
        inline void SerializationOp(Stream &s, Operation ser_action)
        {
            if (ser_action.ForRead())
            {
                str.clear();
                char c = 0;
                while (true)
                {
                    try
                    {
                        READWRITE(c);
                        str.push_back(c);
                    } catch (const std::ios_base::failure &e)
                    {
                        break;
                    }
                }
            } else
            {
                for (size_t i = 0; i < str.size(); i++)
                    READWRITE(str[i]);
            }
        }
    };

    BOOST_AUTO_TEST_CASE(iterator_string_ordering_test)
    {
        BOOST_TEST_MESSAGE("Running Iterator String Ordering Test");

        char buf[10];

        fs::path ph = fs::temp_directory_path() / fs::unique_path();
        CDBWrapper dbw(ph, (1 << 20), true, false, false);
        for (int x = 0x00; x < 10; ++x)
        {
            for (int y = 0; y < 10; y++)
            {
                snprintf(buf, sizeof(buf), "%d", x);
                StringContentsSerializer key(buf);
                for (int z = 0; z < y; z++)
                    key += key;
                uint32_t value = x * x;
                BOOST_CHECK(dbw.Write(key, value));
            }
        }

        std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper &>(dbw).NewIterator());
        for (int seek_start : {0, 5})
        {
            snprintf(buf, sizeof(buf), "%d", seek_start);
            StringContentsSerializer seek_key(buf);
            it->Seek(seek_key);
            for (int x = seek_start; x < 10; ++x)
            {
                for (int y = 0; y < 10; y++)
                {
                    snprintf(buf, sizeof(buf), "%d", x);
                    std::string exp_key(buf);
                    for (int z = 0; z < y; z++)
                        exp_key += exp_key;
                    StringContentsSerializer key;
                    uint32_t value;
                    BOOST_CHECK(it->Valid());
                    if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure
                        break;
                    BOOST_CHECK(it->GetKey(key));
                    BOOST_CHECK(it->GetValue(value));
                    BOOST_CHECK_EQUAL(key.str, exp_key);
                    BOOST_CHECK_EQUAL(value, (uint64_t)(x * x));
                    it->Next();
                }
            }
            BOOST_CHECK(!it->Valid());
        }
    }


BOOST_AUTO_TEST_SUITE_END()
